#include <unistd.h>
#include <fcntl.h>

#include "net_context.h"
#include "tcp_listener.h"
#include "link_node.h"

using namespace usnet;

std::unique_ptr<TCPListener> TCPListener::MakeUnique(EndpointAddr* addr)
{
    auto ctx = NetContext::GetContext();
    std::unique_ptr<TCPListener> tcp_listener { new(std::nothrow) TCPListener { addr } };
    if (tcp_listener == nullptr) {
        LOG_ERROR(ctx, "failed to new TCPListener instance");
        return nullptr;
    }
    LOG_DEBUG(ctx, "new TCPListener instance success");
    if (tcp_listener->InitResource() == false) {
        LOG_ERROR(ctx, "failed to init EThImpl");
        return nullptr;
    }
    LOG_DEBUG(ctx, "TCPListener init resource success");
    tcp_listener->StartListen();
    LOG_DEBUG(ctx, "listening TCP started");
    return tcp_listener;
}

#ifndef NATIVE_SOCKET
TCPListener::~TCPListener()
{
    stop_ = true;
    if (listener_.joinable()) {
        listener_.join();
    }
    fb_mgr_ = nullptr;
    ef_driver_close(ef_res_.handle_);
    addr_ = nullptr;
}

bool TCPListener::InitResource()
{
    auto ctx = NetContext::GetContext();
    if (ef_driver_open(&ef_res_.handle_) != 0) {
        LOG_ERROR(ctx, "ef_driver_open() failed");
        return false;
    }
    const Ifconfig* ifcfg = ctx->GetIfconfig(addr_);
    if (ifcfg->ifindex_ < 0) {
        LOG_ERROR(ctx, "bad ifindex of network interface: %s.", ifcfg->ifname_.c_str());
        return false;
    }
    if (ef_pd_alloc(&ef_res_.pd_, ef_res_.handle_, ifcfg->ifindex_, EF_PD_DEFAULT) != 0) {
        LOG_ERROR(ctx, "ef_pd_alloc() failed");
        return false;
    }
    if (ef_vi_alloc_from_pd(
        &ef_res_.vi_, ef_res_.handle_,
        &ef_res_.pd_, ef_res_.handle_,
        nr_rx_fb_ + nr_tx_fb_, nr_rx_fb_, nr_tx_fb_,
        nullptr, -1, EF_VI_FLAGS_DEFAULT) < 0) {
        LOG_ERROR(ctx, "ef_vi_alloc_from_pd() failed");
        return false;
    }
    fb_mgr_ = FrameBufMgr::MakeShared(&ef_res_, nr_rx_fb_, nr_tx_fb_);
    if (fb_mgr_ == nullptr) {
        LOG_ERROR(ctx, "failed to create FrameBufMgr instance()");
        return false;
    }
    ef_filter_spec fs;
    ef_filter_spec_init(&fs, EF_FILTER_FLAG_NONE);
    if (ef_filter_spec_set_ip4_local(
        &fs, IPPROTO_TCP,
        addr_->be_ip_.v4_,
        addr_->be_port_) != 0) {
       LOG_ERROR(ctx, "ef_filter_spec_set_ip4_local() failed");
        return false;;
    }
    if (ef_vi_filter_add(
        &ef_res_.vi_,
        ef_res_.handle_,
        &fs,
        nullptr) != 0) {
        LOG_ERROR(ctx, "ef_vi_filter_add() failed");
        return false;
    }
    return true;
}

void TCPListener::Listen()
{
    auto ctx = NetContext::GetContext();
    ef_event ev_tab[nr_rx_fb_];
    int nr_ev { 0 };
    while (stop_ == false) {
        usleep(10000);
        nr_ev = ef_eventq_poll(&ef_res_.vi_, ev_tab, nr_rx_fb_);
        for (uint32_t k = nr_ev; k != 0;) {
            switch (EF_EVENT_TYPE(ev_tab[--k])) {
                case EF_EVENT_TYPE_RX:
                {
                    HandleRX(EF_EVENT_RX_RQ_ID(ev_tab[k]), EF_EVENT_RX_BYTES(ev_tab[k]));
                    break;
                }
                case EF_EVENT_TYPE_RX_DISCARD:
                default:
                {
                    LOG_DEBUG(ctx, "unexpected ef event listened");
                }
            }
        }
        ef_vi_receive_push(&ef_res_.vi_);
    }
}

void TCPListener::HandleRX(const uint32_t fb_id, const uint32_t len)
{
    auto ctx = NetContext::GetContext();
    auto fb = fb_mgr_->GetFrameBuf(fb_id);
    EthHeader* eth_hdr = (EthHeader*)fb->mem_addr_;
    int hdr_size { MIN_ETH_HDR };
    if (ntohs(eth_hdr->be_protocol_) == static_cast<uint16_t>(PPT::IPv4)) {
        IP4Header* ip4_hdr = (IP4Header*) ((char*) eth_hdr + hdr_size);
        if (ip4_hdr->protocol_ == static_cast<uint8_t>(PPT::TCP)) {
            hdr_size = static_cast<int>((ip4_hdr->ipvhl_ & 0x0f) << 2);
            TCPHeader* tcp_hdr = (TCPHeader*)((char*)ip4_hdr + hdr_size);
            if (tcp_hdr->tcp_flags_ & SYN_BIT) {
                struct sockaddr_in sockaddr;
                memset(&sockaddr, 0, sizeof(struct sockaddr_in));
                sockaddr.sin_port = tcp_hdr->be_lport_;
                sockaddr.sin_addr.s_addr = ip4_hdr->be_laddr_;
                EndpointAddr addr { sockaddr };
                auto conn = TCPConnection::MakeUnique(
                    addr_,
                    addr,
                    static_cast<int64_t>(ntohl(tcp_hdr->be_syn_seq_)));
                {
                    std::lock_guard<std::mutex> lock { mtx_ };
                    pending_conn_.push(std::move(conn));
                }
            }
        }
    }
    if (ntohs(eth_hdr->be_protocol_) == static_cast<uint16_t>(PPT::IPv6)) {
        // pass
    }
    if (ntohs(eth_hdr->be_protocol_) == static_cast<uint16_t>(PPT::ARP)) {
        // pass
    }
    ef_vi_receive_init(&ef_res_.vi_, fb->dma_addr_, fb->id_);
}
#else
TCPListener::~TCPListener()
{
    stop_ = true;
    if (listener_.joinable()) {
        listener_.join();
    }
    if (listenfd_ >= 0) {
        close(listenfd_);
        listenfd_ = -1;
    }
}

bool TCPListener::InitResource()
{
    auto ctx = NetContext::GetContext();
    if (addr_->inet_proto_ == InetProto::IPv4) {
        struct sockaddr_in server_addr;
        memset(&server_addr, 0, sizeof(struct sockaddr_in));
        server_addr.sin_addr.s_addr = addr_->be_ip_.v4_;
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = addr_->be_port_;
        listenfd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (listenfd_ < 0) {
            LOG_ERROR(ctx, "socket() failed");
            return false;
        }
        if (bind(listenfd_, (struct sockaddr*)&server_addr, sizeof(struct sockaddr_in)) != 0) {
            LOG_ERROR(ctx, "bind() failed");
            return false;
        }
    }
    if (addr_->inet_proto_ == InetProto::IPv6) {
        struct sockaddr_in6 server_addr;
        memset(&server_addr, 0, sizeof(struct sockaddr_in6));
        memcpy(&server_addr.sin6_addr.s6_addr, addr_->be_ip_.v6_, sizeof(addr_->be_ip_.v6_));
        server_addr.sin6_family = AF_INET6;
        server_addr.sin6_port = addr_->be_port_;
        int listenfd_ = socket(AF_INET6, SOCK_STREAM, 0);
        if (listenfd_ < 0) {
            LOG_ERROR(ctx, "socket() failed");
            return false;
        }
        if (bind(listenfd_, (struct sockaddr*)&server_addr, sizeof(struct sockaddr_in6)) != 0) {
            LOG_ERROR(ctx, "bind() failed");
            return false;
        }
    }
    return true;
}

void TCPListener::Listen()
{
    auto ctx = NetContext::GetContext();
    if (listen(listenfd_, SOMAXCONN) != 0) {
        LOG_ERROR(ctx, "listen() failed");
        return;
    }
    int flags = fcntl(listenfd_, F_GETFL, 0);
    fcntl(listenfd_, F_SETFL, flags | O_NONBLOCK);
    while (stop_ == false) {
        socklen_t len { 0 };
        if (addr_->inet_proto_ == InetProto::IPv4) {
            struct sockaddr_in client_addr;
            memset(&client_addr, 0, sizeof(struct sockaddr_in));
            int connfd = accept4(listenfd_, (struct sockaddr*)&client_addr, &len, SOCK_NONBLOCK);
            if (connfd >= 0) {
                EndpointAddr addr { client_addr };
                auto conn = TCPConnection::MakeUnique(addr_, addr, connfd);
                {
                    std::lock_guard<std::mutex> lock { mtx_ };
                    pending_conn_.push(std::move(conn));
                }
            }
        }
        if (addr_->inet_proto_ == InetProto::IPv6) {
            struct sockaddr_in6 client_addr;
            memset(&client_addr, 0, sizeof(struct sockaddr_in6));
            int connfd = accept4(listenfd_, (struct sockaddr*)&client_addr, &len, SOCK_NONBLOCK);
            if (connfd >= 0) {
                EndpointAddr addr { client_addr };
                auto conn = TCPConnection::MakeUnique(addr_, addr, connfd);
                {
                    std::lock_guard<std::mutex> lock { mtx_ };
                    pending_conn_.push(std::move(conn));
                }
            }
        }
    }
}
#endif